package raft

import (
	"errors"
	"fmt"
	"go.uber.org/zap"
	"xin/raft/lifecycle"
	"xin/raft/pb"
)

type KindType uint64 // uint64 考虑内存对齐上可能好一些

const (
	KindNil     KindType = iota // 一条日志都没有的时候的描述，系统刚刚初始化，未写入日志，但需要一个枚举进行描述
	KindNoOp                    // 空日志，Leader当选之后，要记录一条空日志，保证 Leader 节点的日志最新。
	KindGeneral                 // 常规日志，例如，kv服务器请求的写入命令

)

// Entry 日志条目
type Entry struct {
	Kind    KindType // 日志条目类型
	Index   uint64   // 索引
	Term    uint64   // 任期
	Len     int64    // 二进制命令长度
	Command []byte   // 命令
}

func (e *Entry) String() string {
	return fmt.Sprintf("\n{\n	Kind:%d\n	Index:%d\n	Term:%d\n	Len:%d\n	Command:%s\n}", e.Kind, e.Index, e.Term, e.Len, string(e.Command))
}

// LogStore 日志存储接口
type LogStore interface {
	lifecycle.LifeCycle

	FirstIndex() uint64                       // 第一条日志索引
	LastIndex() uint64                        // 最后一条日志索引
	GetLogEntry(index uint64) (*Entry, error) // 获取日志条目
	StoreLogEntry(entry *Entry) error         // 存储日志条目
	StoreLogEntries(entries []*Entry) error   // 存储日志条目
	DeleteRange(minIndex uint64) error        // 删除日志条目
}

// Log Write-Ahead Logging
type wal struct {
	entries     map[uint64]*Entry // 缓存中的日志条目
	commitIndex uint64            // 提交日志索引
	lastIndex   uint64            // 最新日志索引
	logStore    LogStore          // 日志存储接口
	logger      *zap.Logger       // logger
}

func newLog(logStore LogStore, logger *zap.Logger) *wal {
	return &wal{
		entries:  make(map[uint64]*Entry),
		logStore: logStore,
		logger:   logger,
	}
}
func (w *wal) Init() error {
	if err := w.logStore.Init(); err != nil {
		return err
	}

	if err := w.load(); err != nil {
		return err
	}

	return nil
}

func (w *wal) Shutdown() error {
	if err := w.logStore.Shutdown(); err != nil {
		return err
	}

	return nil
}

// load 读取文件中的日志
func (w *wal) load() error {
	firstIndex := w.logStore.FirstIndex()
	w.lastIndex = w.logStore.LastIndex()
	if firstIndex == 0 && w.lastIndex == 0 {
		w.logger.Warn(">>> 日志文件为空")
		return nil
	}

	for i := firstIndex; i <= w.lastIndex; i++ {
		entry, err := w.logStore.GetLogEntry(i)
		if err != nil {
			return err
		}

		w.entries[i] = entry
	}

	w.commitIndex = w.lastIndex // 已经提交的日志索引

	return nil
}

// lastEntry 最新日志条目
func (w *wal) lastEntry() (*Entry, error) {
	return w.getEntry(w.lastIndex)
}

// getEntry 获取日志
func (w *wal) getEntry(index uint64) (*Entry, error) {
	if index == 0 {
		return &Entry{
			Kind:  KindNil,
			Index: 0,
			Term:  0,
		}, nil
	}

	if entry, ok := w.entries[index]; ok {
		return entry, nil
	}

	entry, err := w.logStore.GetLogEntry(index)
	if err != nil {
		return nil, err
	}
	w.entries[index] = entry

	return entry, nil
}

// appendEntries 追加日志
func (w *wal) appendEntries(command *pb.AppendEntries) error {
	err := w.checkPrevLogConflict(command.PrevLogIndex, command.PrevLogTerm) // 校验本地日志与Leader日志是否冲突
	if err != nil {
		return err
	}

	if command.Entries == nil || len(command.Entries) == 0 { // 心跳
		return nil
	}

	newEntries, err := w.removeConflictIndex(command.Entries) // 删除与Leader 不匹配的日志
	if err != nil {
		w.logger.Error(">>> 删除与领导人不符的日志失败", zap.Error(err))
		return err
	}

	if newEntries == nil || len(newEntries) == 0 {
		return nil
	}

	// 追加日志的几种情况
	// 第一种；最正常的，复制 4,5,6 直接匹配成功复制成功
	// leader 	[1,1][1,2][1,3][1,4][1,5][1,6]
	// follower	[1,1][1,2][1,3]

	// 第二种；复制 4,5,6
	// follower 与 leader ， index=3 的任期不同， 代码不会执行到这一步。 checkPrevLogConflict 就返回了。
	// leader 	[1,1][1,2][2,3][1,4][1,5][1,6]
	// follower	[1,1][1,2][1,3]

	// 第三种；复制 4,5,6
	// follower 已经存在索引为 4 的日志，复制的 4,5,6 会先删除 4，只追加 5,6。 removeConflictIndex
	// leader 	[1,1][1,2][1,3][1,4][1,5][1,6]
	// follower	[1,1][1,2][1,3][1,4]

	err = w.appendEntries0(newEntries) // 追加日志
	if err != nil {
		w.logger.Error(">>> 本地追加日志失败", zap.Error(err))
		return err
	}

	err = w.advanceCommitIndex(command.LeaderCommit, command.Term)
	if err != nil {
		w.logger.Error(">>> 推进日志失败", zap.Error(err))
		return err
	}

	return nil
}

// checkPrevLogConflict 检查Leader的上一条日志与本地是否匹配
func (w *wal) checkPrevLogConflict(prevLogIndex uint64, prevLogTerm uint64) error {
	entry, err := w.getEntry(prevLogIndex)
	if err != nil {
		return err
	}

	if entry == nil {
		w.logger.Warn(">>> 日志不存在", zap.Uint64("prevLogIndex", prevLogIndex))
		return fmt.Errorf("日志不存在 prevLogIndex=%d", prevLogIndex)
	}

	if entry.Kind == KindNil { // 本地一条日志都没有
		return nil
	}

	if entry.Term != prevLogTerm {
		w.logger.Warn(">>> 本地日志任期与 prevLog 存在差异", zap.Uint64("prevLogIndex", prevLogIndex), zap.Uint64("prevLogTerm", prevLogTerm))
		return fmt.Errorf("本地日志任期与 prevLog 存在差异 prevLogIndex=%d prevLogTerm=%d", prevLogIndex, prevLogTerm)
	}

	return nil
}

// removeConflictIndex 删除冲突索引
func (w *wal) removeConflictIndex(entries []*pb.Entry) ([]*Entry, error) {
	if entries == nil || len(entries) == 0 {
		return nil, nil
	}

	es := make([]*Entry, 0, len(entries))
	// 验证日志条目，index 是否有顺序性
	for i := 1; i < len(entries); i++ {
		if entries[i].Index != entries[i-1].Index+1 {
			return nil, errors.New(">>> 日志条目是非连续性的")
		}

		es = append(es, &Entry{
			Kind:    KindType(entries[i].Kind),
			Index:   entries[i-1].Index,
			Term:    entries[i].Term,
			Len:     int64(len(entries[i].Command)),
			Command: entries[i].Command,
		})
	}

	// 数组第一位数为 任期， 第二位数为 索引
	// A [1,1] [1,2] [1,3] [1,4],[1,5] [1,6]
	// B [1,1] [1,2] [1,3] [2,4],[2,5] [2,6]
	// C [1,1] [1,2] [1,3]

	// 最早 A 为 Leader, 然后 索引 4,5,6 宕机未同步到B，C
	// B 成为 Leader, 写入 4,5,6. B开始向 A,C 同步
	// A 与 B 的冲突点为 4,5,6 任期不同
	// C 与 B 的冲突点为 4,5,6 C 节点没有

	var conflictIndex uint64 = 0 // 不匹配的日志索引
	ii := 0
	for i, entry := range es {
		e, err := w.getEntry(entry.Index)
		if err != nil {
			return nil, err
		}

		if e == nil || e.Term != entry.Term {
			conflictIndex = entry.Index
			ii = i
			//w.logger.Info("ii", zap.Int("ii", ii))
			break
		}
	}

	if conflictIndex == 0 {
		return make([]*Entry, 0), nil
	}

	// 删除本地冲突索引
	err := w.logStore.DeleteRange(conflictIndex)
	if err != nil {
		return nil, err
	}

	return es[ii:], nil
}

// appendEntries0 追加日志
func (w *wal) appendEntries0(entries []*Entry) error {
	if entries == nil || len(entries) == 0 {
		return nil
	}

	for _, e := range entries {
		w.entries[e.Index] = e
	}

	w.lastIndex = entries[len(entries)-1].Index

	return nil
}

// appendEntry 追加日志
func (w *wal) appendEntry(entry *Entry) error {
	preEntry, err := w.getEntry(entry.Index - 1)
	if err != nil {
		return err
	}

	if preEntry == nil {
		w.logger.Warn(">>> 日志不存在", zap.Uint64("prevLogIndex", entry.Index-1))
		return fmt.Errorf("日志不存在 prevLogIndex=%d", entry.Index-1)
	}

	w.entries[entry.Index] = entry
	w.lastIndex = entry.Index

	return nil
}

// advanceCommitIndex 推进日志
func (w *wal) advanceCommitIndex(commitIndex, commitTerm uint64) error {
	if commitIndex < w.commitIndex {
		w.logger.Error(">>> 推进索引小于本地索引", zap.Uint64("commitIndex", commitIndex), zap.Uint64("localIndex", w.commitIndex))
		return errors.New("推进索引小于本地索引")
	}

	entry, err := w.getEntry(commitIndex)
	if err != nil {
		return err
	}

	if entry == nil || entry.Kind == KindNil {
		w.logger.Error(">>> 推进索引日志未找到", zap.Uint64("commitIndex", commitIndex))
		return errors.New("推进索引日志未找到")
	}

	if entry.Term != commitTerm {
		w.logger.Error(">>> 推进索引任期不符", zap.Uint64("commitTerm", commitTerm), zap.Uint64("localTerm", entry.Term))
		return errors.New("推进索引任期不符")
	}

	// 推进
	entries := make([]*Entry, 0)
	for i := commitIndex; i <= w.lastIndex; i++ {
		entries = append(entries, w.entries[i])
	}

	err = w.logStore.StoreLogEntries(entries)
	if err != nil {
		return err
	}

	w.commitIndex = commitIndex

	return nil
	// TODO 应用到状态机
}
